#pragma once
#include <type_traits>
#include <functional>
#include <tuple>

template<int... Indexs>
struct tuple_index{};

template<int N, int... Indexs>
struct make_tuple_index : make_tuple_index<N - 1, N - 1, Indexs...>{};
template<int... Indexs>
struct make_tuple_index<0, Indexs...>
{
	using type = tuple_index<Indexs...>;
};

template<typename T>
using decay_t = typename std::decay<T>::type;

template<typename T>
struct function_traits;

template<typename Ret, typename... Args>
struct function_traits<Ret(Args...)>
{
    enum { args_count = sizeof...(Args) };
    using args_tuple = std::tuple<decay_t<Args>...>;
    using args_tuple_indexs = typename make_tuple_index<sizeof...(Args)>::type;
    typedef Ret return_type;
    using std_function_type = std::function<Ret(Args...)>;
    //typedef Ret function_type(Args...);
    //typedef Ret(*pointer)(Args...);
};

template<typename Ret, typename... Args>
struct function_traits<Ret(*)(Args...)> : function_traits<Ret(Args...)>{};

template <typename Ret, typename... Args>
struct function_traits<std::function<Ret(Args...)>> : function_traits<Ret(Args...)>{};

template <typename ReturnType, typename ClassType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...)> : function_traits<ReturnType(Args...)>{};

template <typename ReturnType, typename ClassType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> : function_traits<ReturnType(Args...)>{};

template<typename Callable>
struct function_traits : function_traits<decltype(&Callable::operator())>{};

template<typename F, typename ... Args, int ... Indexs>
typename function_traits<F>::return_type call_function(F &func, std::tuple<Args...> &tp, tuple_index<Indexs...>)
{
	return func(std::get<Indexs>(tp)...);
}

template<typename F, typename Self, typename ... Args, int ... Indexs>
typename function_traits<F>::return_type call_function(F &member_func, Self *self, std::tuple<Args...> &tp, tuple_index<Indexs...>)
{
	return (self->*member_func)(std::get<Indexs>(tp)...);
}


template<typename F>
auto wrapper(F && func, typename std::enable_if<std::is_lvalue_reference<F>::value && std::is_const<F>::value, int>::type = 0)->decltype(std::cref(func))
{
	return std::cref(func);
}
template<typename F>
auto wrapper(F && func, typename std::enable_if<std::is_lvalue_reference<F>::value && !std::is_const<F>::value, int>::type = 0)->decltype(std::ref(func))
{
	return std::ref(func);
}
template<typename F>
F wrapper(F && func, typename std::enable_if<std::is_pointer<F>::value, int>::type = 0)
{
	return func;
}
template<typename F>
auto wrapper(F && func, typename std::enable_if<!std::is_lvalue_reference<F>::value && !std::is_pointer<F>::value, int>::type = 0)->decltype(std::move(func))
{
	return std::move(func);
}
